import { CreateElement, VNode, VNodeChildren } from 'vue';
import { Vue, Component, Prop } from 'vue-property-decorator';

import {
  ConfigType,
  ContextExpression,
  ValidationResult,
  isBoolean,
  isString,
  isNumber,
  runExpression,
  getControl,
  getBehaviorByKey,
} from '../../vendors/handie';
import { ViewFieldDescriptor } from '../../vendors/handie/types/input';

import FieldRenderer from '../field';
import { resolveFieldBehavior } from './helper';

@Component({
  // @ts-ignore
  abstract: true,
})
export default class FormRenderer extends Vue {
  @Prop({ type: Array, default: () => [] })
  private readonly fields!: ViewFieldDescriptor[];

  @Prop({ type: Object, default: () => ({}) })
  private readonly value!: Record<string, any>;

  @Prop({ type: Boolean, default: false })
  private readonly readonly!: boolean;

  @Prop({ type: Object, default: () => ({}) })
  private readonly validation!: Record<string, ValidationResult>;

  @Prop({ type: Object, default: () => ({}) })
  private readonly config!: ConfigType;

  @Prop()
  private readonly className!: any;

  private isTrue(expression: ContextExpression, defaultReturnValue?: any): boolean {
    return !!runExpression({ value: this.value }, expression, defaultReturnValue);
  }

  private resolveReadonly(readonly?: boolean | ContextExpression): boolean {
    if (isBoolean(readonly)) {
      return readonly as boolean;
    }

    return isString(readonly) ? this.isTrue(readonly as string) : false;
  }

  private resolveFieldRequired(
    readonly?: boolean,
    required?: boolean | ContextExpression,
  ): boolean {
    if (readonly) {
      return false;
    }

    return isString(required) ? this.isTrue(required as string) : (required as boolean);
  }

  private resolveLabelWidth(): string {
    const labelWidth =
      this.config.formControlLabelWidth ||
      getBehaviorByKey('common.view.objectViewFormControlLabelWidth', '');

    return isNumber(labelWidth) ? `${labelWidth}px` : labelWidth;
  }

  private resolveFormControlSize(): string {
    return (
      this.config.formControlSize || getBehaviorByKey('common.view.objectViewFormControlSize', '')
    );
  }

  private resolveShowValidationMessage(): boolean {
    const showValidationMessage = this.config.showValidationMessage;

    return isBoolean(showValidationMessage)
      ? showValidationMessage
      : getBehaviorByKey('common.view.objectViewShowValidationMessage');
  }

  private resolveFormItems(h: CreateElement): VNode[] {
    const formItems: VNode[] = [];

    this.fields.forEach(field => {
      if (field.hidden) {
        return;
      }

      const { name, label, available, hint, config = {} } = field;

      if (isString(available) && !this.isTrue(available!, false)) {
        return;
      }

      const fieldValidation = this.validation[name] || { success: true };
      const readonly = this.resolveReadonly(this.readonly || field.readonly);

      const formItemProps: Record<string, any> = {
        required: this.resolveFieldRequired(readonly, field.required),
        message: fieldValidation.success ? '' : fieldValidation.message,
      };

      const formItemChildren: VNodeChildren = [
        h(FieldRenderer, {
          props: {
            field,
            value: this.value[name],
            readonly,
          },
          on: this.$listeners,
        }),
      ];

      const resolveBehavior = resolveFieldBehavior.bind(null, config);

      if (resolveBehavior('showHintAtFormItem', false) === true) {
        if (resolveBehavior('hintPositionOfFormItem', 'explain') === 'label' && hint) {
          formItemChildren.push(
            h('span', { slot: 'label' }, [
              label,
              h(getControl('Tooltip'), { props: { content: hint } }, [
                h(getControl('Icon'), { props: { refs: resolveBehavior('hintIcon', '') } }),
              ]),
            ]),
          );
        } else {
          formItemProps.label = label;
          formItemProps.hint = hint;
        }
      } else {
        formItemProps.label = label;
      }

      formItems.push(h(getControl('FormField'), { props: formItemProps }, formItemChildren));
    });

    return formItems;
  }

  private render(h: CreateElement): VNode {
    return h(
      getControl('Form'),
      {
        props: {
          className: this.className,
          labelOption: { width: this.resolveLabelWidth(), align: 'right' },
          controlSize: this.resolveFormControlSize(),
          hideMessage: !this.resolveShowValidationMessage(),
        },
      },
      this.resolveFormItems(h),
    );
  }
}
